/*
 * Copyright ShangHai ECLUB Information Technology Co.,Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.eclub.emotion.rootstone.server.deploy;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

/**
 * 
 * TODO Description
 * 
 * @author jun.yang, Wincor Nixdorf International GmbH
 */
public class DynamicClassLoader extends SecureClassLoader {
	private static Logger LOGGER = Logger.getLogger(DynamicClassLoader.class);

	private static List<String> jarList = new LinkedList<String>();
	private Map<String, Class<?>> classCache = new HashMap<String, Class<?>>();

	public DynamicClassLoader(ClassLoader parent) {
		super(parent);
	}

	public DynamicClassLoader() {

	}

	public Class<?> findClass(String jarPath, String className, boolean fromCache) throws ClassNotFoundException {

		LOGGER.debug("find class jarPath: " + jarPath + "  className: " + className + "  fromCache:" + fromCache);

		if (fromCache && classCache.containsKey(className)) {
			return classCache.get(className);
		}

		String classPath = className.replace('.', '/').concat(".class");
		byte[] clsByte = null;
		if (jarPath == null || jarPath.equalsIgnoreCase("")) {
			for (String jp : jarList) {
				jarPath = jp;
				clsByte = getClassByte(jp, classPath);
				if (clsByte != null) {
					break;
				}
			}
		} else {
			clsByte = getClassByte(jarPath, classPath);
		}

		if (clsByte == null) {
			throw new ClassNotFoundException(className);
		}

		URL url = null;
		try {
			url = new URL("file", "", jarPath);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		return findClass(className, clsByte, url);
	}

	public Class<?> findClass(String className, byte[] clsByte, URL url) {
		Class<?> cls = null;
		CodeSource cs = new CodeSource(url, (java.security.cert.Certificate[]) null);
		ProtectionDomain pd = new ProtectionDomain(cs, null, this, null);
		cls = super.defineClass(className, clsByte, 0, clsByte.length, pd);
		resolveClass(cls);
		classCache.put(className, cls);
		return cls;
	}

	public Class<?> findClass(String jarPath, String className) throws ClassNotFoundException {
		return findClass(jarPath, className, true);
	}

	@Override
	public Class<?> findClass(String className) throws ClassNotFoundException {
		return findClass("", className, true);
	}

	public Class<?> findClass(String className, boolean fromCache) throws ClassNotFoundException {
		return findClass("", className, fromCache);
	}

	public void clearAllClassCache() {
		for (Iterator<Map.Entry<String, Class<?>>> it = classCache.entrySet().iterator(); it.hasNext();) {
			Map.Entry<String, Class<?>> entry = (Map.Entry<String, Class<?>>) it.next();
			LOGGER.debug("key:" + entry.getKey() + "  value:" + entry.getValue().getName());
		}
		classCache.clear();
	}

	public void addURL(String url) {
		if (!jarList.contains(url)) {
			jarList.add(url);
		}
	}

	public void addFolder(String... dirs) throws IOException {
		for (String dir : dirs) {
			Collection<File> files = FileUtils.listFiles(new File(dir), new String[] { "rar", "jar", "war", "ear" }, true);
			for (Iterator<File> it = files.iterator(); it.hasNext();) {
				addURL(it.next().getCanonicalPath());
			}
		}
	}

	public List<String> getJarList() {
		return jarList;
	}

	private byte[] getClassByte(String jarPath, String classPath) {
		JarFile jarFile = null;
		InputStream input = null;
		byte[] clsByte = null;
		try {
			jarFile = new JarFile(jarPath); // read jar
			JarEntry entry = jarFile.getJarEntry(classPath); // read class file
			if (entry != null) {
				LOGGER.debug("get class:" + classPath + "  from:" + jarPath);
				input = jarFile.getInputStream(entry);
				clsByte = new byte[input.available()];
				input.read(clsByte);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			if (jarFile != null) {
				try {
					jarFile.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return clsByte;
	}
}
